
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=utf-8">
<title>Accessible Menu Button</title>

<style type="text/css">
/*margin and padding on body element
  can introduce errors in determining
  element position and are not recommended;
  we turn them off as a foundation for YUI
  CSS treatments. */
body {
	margin:0;
	padding:0;
}
</style>

<link type="text/css" rel="stylesheet" href="../../build/cssfonts/fonts-min.css" />
<script type="text/javascript" src="../../build/yui/yui-min.js"></script>


<!--begin custom header content for this example-->
<style type="text/css">

	/*	The following two styles are necessary to override style rules in the 
		YUI CSS file. */

	#example ul {
		margin: 0;
	}

	#example a:hover {
		text-decoration: none;
	}


	/*	Hide the list while it is being transformed into a menu.	*/

	.yui-loading #menu-1,
	.yui-loading #button-1 {
		display: none;
	}
	
</style>

<script type="text/javascript">

	//	Add a class to the documentElement to prevent the user from seeing 
	//	the unstyled menu while the necessary CSS and JavaScript 
	//	dependancies are being fetched.

	document.documentElement.className = "yui-loading";

</script>
<!--end custom header content for this example-->

</head>

<body class=" yui-skin-sam">

<h1>Accessible Menu Button</h1>

<div class="exampleIntro">
	<p>
This example illustrates how to use the Focus Manager Node Plugin, 
Event's <a href="../../api/YUI.html#event_delegate">delegation support</a> and 
<a href="../../api/YUI.html#event_mouseenter">mouseenter</a> event, along with 
the <a href="../../overlay/">Overlay widget</a> and Node's support for the 
<a href="http://www.w3.org/TR/wai-aria/">WAI-ARIA Roles and States</a> to
create an accessible menu button.
</p>			
</div>

<!--BEGIN SOURCE CODE FOR EXAMPLE =============================== -->

<a id="button-1" href="#menu-1"><span><span>Move To</span></span></a>
<div id="menu-1">
	<ul>
		<li><input type="button" name="button-1" value="Inbox"></li>
		<li><input type="button" name="button-2" value="Archive"></li>
		<li><input type="button" name="button-3" value="Trash"></li>
	</ul>        	
</div>

<!-- YUI Seed -->
<script type="text/javascript" src="../../build/yui/yui.js"></script>
<script type="text/javascript">

	YUI({	
		base:"../../build/", 
		timeout: 10000,
		modules: {
			"menubutton-css": {
				type: "css",
				fullpath: "assets/menubutton.css"
			}    
		}

	}).use("menubutton-css", "node-focusmanager", "node-event-simulate", "overlay", function(Y) {

		var menuButton = Y.Node.get("#button-1"),
			menu;


		var initMenu = function () {

			menu = new Y.Overlay({
				contentBox: "#menu-1",
				visible: false,
				tabIndex: null
			});

			menu.render();


			Y.Node.get("#menu-1").setStyle("display", "");

			var boundingBox = menu.get("boundingBox"),
				contentBox = menu.get("contentBox");

			boundingBox.addClass("yui-buttonmenu");
			contentBox.addClass("yui-buttonmenu-content");


			// Append a decorator element to the bounding box to render the shadow.

			boundingBox.appendChild(Y.Node.create('<div class="yui-menu-shadow"></div>'));

			
			//	Apply the ARIA roles, states and properties to the menu.

			boundingBox.setAttrs({
				role: "menu",
				"aria-labelledby": menuButton.get("id")
			});

			boundingBox.queryAll("input").set("role", "menuitem");


			//	For NVDA: Add the role of "presentation" to each LI 
			//	element to prevent NVDA from announcing the 
			//	"listitem" role.

			boundingBox.queryAll("div,ul,li").set("role", "presentation");
			

			//	Use the FocusManager Node Plugin to manage the focusability
			//	of each menuitem in the menu.

			contentBox.plug(Y.Plugin.NodeFocusManager, { 

					descendants: "input",
					keys: { next: "down:40", // Down arrow
							previous: "down:38" },	// Up arrow
					focusClass: {
						className: "yui-menuitem-active",
						fn: function (node) {
							return node.get("parentNode");
						}
					},
					circular: true

				});


			//	Subscribe to the change event for the "focused" attribute
			//	to listen for when the menu initially gains focus, and 
			//	when the menu has lost focus completely.

			contentBox.focusManager.after("focusedChange", function (event) {

				if (!event.newVal) {	// The menu has lost focus

					//	Set the "activeDescendant" attribute to 0 when the 
					//	menu is hidden so that the user can tab from the 
					//	button to the first item in the menu the next time 
					//	the menu is made visible.
					
					this.set("activeDescendant", 0);

				}

			});
			

			//	Hide the button's menu if the user presses the escape key
			//	while focused either on the button or its menu.

			Y.on("key", function () {

				menu.hide();
				menuButton.focus();				

			}, [menuButton, boundingBox] ,"down:27");
			

			if (Y.UA.ie === 6) {

				//	Set the width and height of the menu's bounding box -  
				//	this is necessary for IE 6 so that the CSS for the 
				//	shadow element can simply set the shadow's width and 
				//	height to 100% to ensure that dimensions of the shadow 
				//	are always sync'd to the that of its parent menu.

				menu.on("visibleChange", function (event) {

					if (event.newVal) {

						boundingBox.setStyles({ height: "", width: "" });

						boundingBox.setStyles({ 
							height: (boundingBox.get("offsetHeight") + "px"), 
							width: (boundingBox.get("offsetWidth") + "px") });
					
					}
				
				});
			
			}


			menu.after("visibleChange", function (event) {
			
				var bVisible = event.newVal;
			
				//	Focus the first item when the menu is made visible
				//	to allow users to navigate the menu via the keyboard
				
				if (bVisible) {

					//	Need to set focus via a timer for Webkit and Opera
					Y.Lang.later(0, contentBox.focusManager, contentBox.focusManager.focus);
					
				}

				boundingBox.set("aria-hidden", (!bVisible));
				
			});				
			

			//	Hide the menu when one of menu items is clicked.

			Y.on("delegate", function (event) {

				var oTarget = event.currentTarget;
				
				alert("You clicked " + oTarget.query("input").get("value"));
			
				contentBox.focusManager.blur();
				menu.hide();

			}, boundingBox, "click", "li");
			

			//	Focus each menuitem as the user moves the mouse over 
			//	the menu.

			Y.on("mouseenter", function (event) {

				var focusManager = contentBox.focusManager;

				if (focusManager.get("focused")) {
					focusManager.focus(event.currentTarget.query("input"));
				}

			}, boundingBox, "li");
			

			//	Hide the menu if the user clicks outside of it or if the 
			//	user doesn't click on the button

			boundingBox.get("ownerDocument").on("mousedown", function (event) {
				
				var oTarget = event.currentTarget;
				
				if (!oTarget.compareTo(menuButton) && 
					!menuButton.contains(oTarget) && 
					!oTarget.compareTo(boundingBox) && 
					!boundingBox.contains(oTarget)) {

					menu.hide();

				}
				
			});
			
		};


		menuButton.addClass("yui-menubutton");


		//	Hide the list until it is transformed into a menu
		
		Y.Node.get("#menu-1").setStyle("display", "none");


		//	Remove the "yui-loading" class from the documentElement
		//	now that the necessary YUI dependencies are loaded.

		menuButton.get("ownerDocument").get("documentElement").removeClass("yui-loading");
		

		//	Apply the ARIA roles, states and properties to the anchor.

		menuButton.setAttrs({
			role: "button",
			"aria-haspopup": true
		});


		//	Remove the "href" attribute from the anchor element to  
		//	prevent JAWS and NVDA from reading the value of the "href"
		//	attribute when the anchor is focused.
		
		if ((Y.UA.gecko || Y.UA.ie) && navigator.userAgent.indexOf("Windows") > -1) {

			menuButton.removeAttribute("href");

			//	Since the anchor's "href" attribute has been removed, the 
			//	element will not fire the click event in Firefox when the 
			//	user presses the enter key.  To fix this, dispatch the 
			//	"click" event to the anchor when the user presses the 
			//	enter key.

			Y.on("key", function (event) {

				event.currentTarget.simulate("click");

			}, menuButton, "down:13");

		}


		//	Set the "tabIndex" attribute of the anchor element to 0 to 
		//	place it in the browser's default tab flow.  This is 
		//	necessary since 1) anchor elements are not in the default 
		//	tab flow in Opera and 2) removing the "href" attribute  
		//	prevents the anchor from firing its "click" event 
		//	in Firefox.

		menuButton.set("tabIndex", 0);


		var showMenu = function (event) {

			//	For performance: Defer the creation of the menu until 
			//	the first time the button is clicked.

			if (!menu) {
				initMenu();
			}


			if (!menu.get("visible")) {

                menu.set("align", {
                    node: menuButton,
                    points: [Y.WidgetPositionExt.TL, Y.WidgetPositionExt.BL]
                });

				menu.show();

			}

			//	Prevent the anchor element from being focused 
			//	when the users mouses down on it.
			event.preventDefault();

		}; 


		//	Bind both a "mousedown" and "click" event listener to 
		//	ensure the button's menu can be invoked using both the 
		//	mouse and the keyboard.
		
		menuButton.on("mousedown", showMenu);
		menuButton.on("click", showMenu);

	});

</script>

<!--END SOURCE CODE FOR EXAMPLE =============================== -->

</body>
</html>
